package color;


import geom.Geom;
import geom.PolarVector;
import geom.Vector;

import java.util.ArrayList;
import java.util.Comparator;

import java.util.Iterator;

//import org.apache.log4j.Logger;
//import org.apache.log4j.Level;
import org.grlea.log.*;

import cluster.Clusterable;

import processing.core.PApplet;
import proxml.XMLElement;

public class Color implements Clusterable{
	/**
	 * Logger for this class
	 */
	//private static final Logger logger = Logger.getLogger(Color.class);
	
	public enum ColorMode {RGB, HSV, Lab;}
	public static ColorMode colorMode = ColorMode.Lab;
	
	
	private boolean hsvUpdated = false;
	private Rgb rgb;
	
	private boolean rgbUpdated = false;
	private Hsv hsv;

	private boolean labUpdated = false;
	private Lab lab;
	
/*	--------------------------------------------------------------
 * 	Constructors
 	--------------------------------------------------------------	*/
	
	public Color(){
		rgb = new Rgb(0, 0, 0);
		rgbUpdated = false;
		hsv = new Hsv(0, 0, 0);
		hsvUpdated = false;
		lab = new Lab(0, 0, 0);
		labUpdated = false;
	}
	
	public Color(Rgb rgb){
		setRgb(rgb);
	}
	
	public Color(Hsv hsv){
		setHsv(hsv);
	}
	
	public Color(Lab lab){
		setLab(lab);
	}
	
	public Color(Color c){
		set(c);
	}
	
	public Color(int c){
		setRgb(c);
	}

	
	
/*	--------------------------------------------------------------
 * 	Setters & Getters
 	--------------------------------------------------------------	*/

	
	public float getRed() {
		checkRgb();
		return rgb.r;
	}
	public float getGrn() {
		checkRgb();
		return rgb.g;
	}
	public float getBlu() {
		checkRgb();
		return rgb.b;
	}
	public Rgb getRgb(){
		checkRgb();
		return rgb;
	}
	public int getRGBVal(){
		checkRgb();
		return ColorConverter.packRgb(rgb);
	}
	
	public float getHue() {
		checkHsv();
		return hsv.h;
		
	}
	public float getSat() {
		checkHsv();
		return hsv.s;
	}
	public float getVal() {
		checkHsv();
		return hsv.v;
	}
	
	public Hsv getHsv(){
		checkHsv();
		return hsv;
	}
	
	public float getL() {
		checkLab();
		return lab.L;
		
	}
	public float getA() {
		checkLab();
		return lab.a;
	}
	public float getB() {
		checkLab();
		return lab.b;
	}
	
	public Lab getLab(){
		checkLab();
		return lab;
	}
	
	public void setRed(float red) {
		checkRgb();
		rgbSet();
		this.rgb.r = red;
	}
	public void setGrn(float grn) {
		checkRgb();
		rgbSet();
		this.rgb.g = grn;
	}
	public void setBlu(float blu) {
		checkRgb();
		rgbSet();
		this.rgb.b = blu;
	}
	public void setRgb(Rgb rgb){
		rgbSet();
		this.rgb = new Rgb(rgb);
	}
	public void setRgb(int rgb){
		setRgb(ColorConverter.unpackRgb(rgb));
	}


	public void setHue(float hue) {
		checkHsv();
		hsvSet();
		this.hsv.h = hue;
	}

	public void setSat(float sat) {
		checkHsv();
		hsvSet();
		this.hsv.s = sat;
	}
	public void setVal(float val) {
		checkHsv();
		hsvSet();
		this.hsv.v = val;
	}
	public void setHsv(Hsv hsv){
		hsvSet();
		hsv = new Hsv(hsv);
	}
	
	public void setL(float l){
		checkLab();
		labSet();
		lab.L = l;
	}
	public void setA(float a){
		checkLab();
		labSet();
		lab.a = a;
	}
	public void setB(float b){
		checkLab();
		labSet();
		lab.b = b;
	}
	
	public void setLab(Lab lab){
		labSet();
		this.lab = new Lab(lab);
	}
	
	public void set(Color c){
		hsvUpdated = c.hsvUpdated;
		rgbUpdated = c.rgbUpdated;
		labUpdated = c.labUpdated;
		if(hsvUpdated) hsv = new Hsv(c.hsv);
		if(rgbUpdated) rgb = new Rgb(c.rgb);
		if(labUpdated) lab = new Lab(c.lab);
	}

	

/*	--------------------------------------------------------------
 * 	Color Functions
 	--------------------------------------------------------------	*/	
	

	public static Color complementary(Color c){
		Color color = new Color(c);
		color.setHue(c.getHue() + Hsv.HUE_RANGE / 2f);
		return color;
	}
	
	public static Color oppositeBrightness(Color c){
		switch(colorMode){
		case RGB: 
			Rgb rgb = c.getRgb();
			return new Color(rgb.getOppositeBrightness());
		case HSV: 
			Hsv hsv = c.getHsv();
			return new Color(hsv.getOppositeBrightness());
		case Lab: 
			Lab lab = c.getLab();
			return new Color(lab.getOppositeBrightness());
		default: return null;
		}
	}
	
	
	
/*	--------------------------------------------------------------
 * 	Clusterable Interface implementation
 	--------------------------------------------------------------	*/
	
	public Clusterable getClone() {
		return new Color(this);
	}
	
	public void addTo(Clusterable b){
		if(b instanceof Color){
			hsv.h += ((Color)b).hsv.h;
			hsv.s += ((Color)b).hsv.s;
			hsv.v += ((Color)b).hsv.v;
		}
	}
	public void divBy(float d){
		hsv.h /= d;
		hsv.s /= d;
		hsv.v /= d;
	}

	public float distanceTo(Clusterable b) {
		switch(colorMode){
		case RGB: return getRgb().distanceTo(((Color)b).getRgb(), Rgb.Metric.SQUARED);
		case HSV: return getHsv().distanceTo(((Color)b).getHsv());
		case Lab: return getLab().distanceTo(((Color)b).getLab(), Lab.Metric.DELTA_E_1994);
		default: return Float.NaN;
		}
	}
	
	public Vector getDirectionVector(Clusterable b){
		switch(colorMode){
		case RGB: return getRgb().directionVector(((Color)b).getRgb());
		case HSV: return getHsv().directionVector(((Color)b).getHsv());
		case Lab: return getLab().directionVector(((Color)b).getLab());
		default: return null;
		}
	}
	public Vector sortVector;
	public void setSortVector(Vector v){
		sortVector = v;
	}
	
	public Vector getVector(){
		switch(colorMode){
		case RGB: return getRgb().getVector();
		case HSV: return getHsv().getVector();
		case Lab: return getLab().getVector();
		default: return null;
		}
	}
	
	public int compareTo(Object o){
		Color c = (Color)o;
		float val, cVal;
		switch(colorMode){
		case RGB: 
			val = getRgb().getVectorPos(sortVector);
			cVal = c.getRgb().getVectorPos(sortVector);
			break;
		case HSV: 
			val = getHsv().getVectorPos(sortVector);
			cVal = c.getHsv().getVectorPos(sortVector);
			break;
		default:
		case Lab: 
			val = getLab().getVectorPos(sortVector);
			cVal = c.getLab().getVectorPos(sortVector);
			break;
		}
		if(val > cVal) return -1;
		if(cVal > val) return 1;
		return 0;
	}
	
	public Clusterable newEmpty(){
		return new Color();
	}
	
	@SuppressWarnings("unchecked")
	public void setCentroid(ArrayList<? extends Clusterable> list){
		
		switch(colorMode){
		case RGB: setRgb(Rgb.average(getRgbArray((ArrayList<Color>)list))); break;
		case Lab: setLab(Lab.average(getLabArray((ArrayList<Color>)list))); break;
		default: setRgb(Rgb.average(getRgbArray((ArrayList<Color>)list))); break;
		}
	}
	
	public static ArrayList<Color> getColorArrayFromRgb(ArrayList<Rgb> list){
		ArrayList<Color> color = new ArrayList<Color>();
		Iterator<Rgb> i = list.iterator();
		while(i.hasNext()) color.add(new Color(i.next()));
		return color;
	}
	
	public static ArrayList<Color> getColorArrayFromHsv(ArrayList<Hsv> list){
		ArrayList<Color> color = new ArrayList<Color>();
		Iterator<Hsv> i = list.iterator();
		while(i.hasNext()) color.add(new Color(i.next()));
		return color;
	}
	
	public static ArrayList<Color> getColorArrayFromLab(ArrayList<Lab> list){
		ArrayList<Color> color = new ArrayList<Color>();
		Iterator<Lab> i = list.iterator();
		while(i.hasNext()) color.add(new Color(i.next()));
		return color;
	}
	
	public static ArrayList<Rgb> getRgbArray(ArrayList<Color> list){
		ArrayList<Rgb> rgb = new ArrayList<Rgb>();
		Iterator<Color> i = list.iterator();
		while(i.hasNext()) rgb.add(i.next().getRgb());
		return rgb;
	}
	public static ArrayList<Hsv> getHsvArray(ArrayList<Color> list){
		ArrayList<Hsv> hsv = new ArrayList<Hsv>();
		Iterator<Color> i = list.iterator();
		while(i.hasNext()) hsv.add(i.next().getHsv());
		return hsv;
	}
	public static ArrayList<Lab> getLabArray(ArrayList<Color> list){
		ArrayList<Lab> lab = new ArrayList<Lab>();
		Iterator<Color> i = list.iterator();
		while(i.hasNext()) lab.add(i.next().getLab());
		return lab;
	}
	
/*	--------------------------------------------------------------
 * 	Color Metrics
 	--------------------------------------------------------------	*/
	
	private void rgbSet(){
		rgbUpdated = true;
		hsvUpdated = false;
		labUpdated = false;
	}
	
	private void hsvSet(){
		rgbUpdated = false;
		hsvUpdated = true;
		labUpdated = false;
	}

	private void labSet(){
		rgbUpdated = false;
		hsvUpdated = false;
		labUpdated = true;
	}

	private void checkRgb(){
		if(rgb != null && rgbUpdated) return;
		rgbUpdated = true;
		if(hsvUpdated) 
			rgb = new Rgb(ColorConverter.hsvToRgb(hsv)); 
		else if(labUpdated)
			rgb = new Rgb(ColorConverter.labToRgb(lab));
		else rgb = new Rgb(0, 0, 0);
		
	}
	
	private void checkHsv(){
		if(hsv != null && hsvUpdated) return;
		hsvUpdated = true;
		if(rgbUpdated)
			hsv = new Hsv(ColorConverter.rgbToHsv(rgb));
		else if(labUpdated)
			hsv = new Hsv(ColorConverter.labToHsv(lab));
		else hsv = new Hsv(0, 0, 0);
	}
	
	private void checkLab(){
		if(lab != null && labUpdated) return;
		labUpdated = true;
		if(rgbUpdated)
			lab = new Lab(ColorConverter.rgbToLab(rgb));
		else if(hsvUpdated)
			lab = new Lab(ColorConverter.hsvToLab(hsv));
		else lab = new Lab(0, 0, 0);
	}
	
	public String toString(){
		String str = "Color:";
		if(rgbUpdated)
			str += rgb.toString();
		if(hsvUpdated)
			str += hsv.toString();
		if(labUpdated)
			str += lab.toString();
		return str;
	}
	
	public void draw(PApplet pa, float x, float y, float w, float h){
		draw(pa, x, y, w, h, false);
	}

	
	public void draw(PApplet pa, float x, float y, float w, float h, boolean selected){
		
		if(!selected) pa.noStroke();
		else {
			pa.strokeWeight(2);
			oppositeBrightness(complementary(this)).stroke(pa);
		}
		fill(pa);
		pa.rect(x, y, w, h);
	}
	
	public void fill(PApplet pa){
		getRgb().fill(pa);
	}
	
	public void stroke(PApplet pa){
		getRgb().stroke(pa);
	}
	
	public void emissive(PApplet pa){
		getRgb().emissive(pa);
	}
	
	public void specular(PApplet pa){
		getRgb().specular(pa);
	}
	public void ambient(PApplet pa){
		getRgb().ambient(pa);
	}
	
	public XMLElement toXml(){
		XMLElement elem = new XMLElement("clr");
		checkLab();
		elem.addAttribute("rgb", ColorConverter.packRgb(getRgb()));
		elem.addAttribute("L", lab.L);
		elem.addAttribute("a", lab.a);
		elem.addAttribute("b", lab.b);
		//elem.addChild(getHsv().toXML());
		//elem.addChild(getLab().toXML());

		return elem;
	}
	
	
}
